﻿using md.imi.membranes.objects.membranes;
using md.imi.membranes.objects.strings;

namespace md.imi.membranes.objects.rules
{
    using System;
    using System.Collections.Generic;
    using md.imi.membrane.common.Interfaces;
    using md.imi.membrane.constants;
    using md.imi.membrane.objects.Exceptions;

    [Serializable]
    public class ReplicationRule : Rule, IValidator, IRuleSelection, IRuleExecution
    {
        public LeftHandRule LeftHandRule { get; set; }

        public List<RightHandRule> RightHandRules { get; set; }

        public MembranesTree ParentMembrane { get; set; }

        /// <summary>
        ///     Initializes a new instance of ReplicationRule.
        /// </summary>
        public ReplicationRule()
        {
            this.LeftHandRule = new LeftHandRule();
            this.RightHandRules = new List<RightHandRule>();
        }

        public ReplicationRule(MembranesTree container)
            : this()
        {
            ParentMembrane = container;
        }

        public ReplicationRule(ReplicationRule rule) : base(rule)
        {
            this.LeftHandRule = new LeftHandRule(rule.LeftHandRule);
            this.RightHandRules  = new List<RightHandRule>();
            foreach(var rhr in rule.RightHandRules)
            {
                this.RightHandRules.Add(new RightHandRule(rhr));
            }

            this.ParentMembrane = rule.ParentMembrane;
        }
        
        public override Rule GetCopy ()
        {
            var newRule = new ReplicationRule(this);
            return newRule;
        }

        /// <summary>
        ///     Calculate available solution for selected string object.
        /// The upper layer calls this method for a selected membrane.
        /// In this case, it is taken each string object, and for it are
        /// applied all rules, to find all possible solutions.
        /// As a result it is obtained a dictionary of strings as key, with
        /// a list of possible solutions for each string (all rules are verified
        /// for each string object).
        /// </summary>
        /// <param name="currentMembrane"></param>
        ///
        /// <param name="stringObjectIndex">the index of processed string object from multiset</param>
        /// <returns>A list of sollutions which are available for passed string object and selected
        /// rule.</returns>
        public override List<Solution> CalculateAvailableSolutions(Membrane currentMembrane, int stringObjectIndex)
        {
            var result = new List<Solution>();

            if (currentMembrane == null || LeftHandRule == null || RightHandRules == null)
                return result;

            /*
                // this cheking is required only for p-systems with changeable rules or structure.
            foreach (var rightHandRule in RightHandRules)
            {
                 if (!currentMembrane.MembraneIsAcessibleForInRule(rightHandRule))
                    return new List<Solution>();
            }
            */

            StringObject stringObject = currentMembrane.Multiset[stringObjectIndex];
            if (stringObject == null) return new List<Solution>();	// check for PRG error
            Console.WriteLine("Calculate solution for rule: " + this);
            int substringIndex = -1;
            do
            {
                substringIndex = stringObject.getSubstringIndex(substringIndex + 1,
                                                            LeftHandRule.SubstringPattern);

                if (substringIndex > -1)
                {
                    var solution = new Solution
                    {
                        AppliedRule = this,
                        ProcessedString = new StringObject(stringObject),
                        ProcessedStringIndex = stringObjectIndex,
                        ProcessedSubStringIndex = substringIndex,
                        Membrane = currentMembrane
                    };

                    Console.WriteLine("### Solution found : " + solution);

                    result.Add(solution);
                }
            } while (substringIndex > -1);

            return result;
        }

        /// <summary>
        ///     Take a early calculated solution and apply it
        /// </summary>
        /// <param name="solution">early calculated solution</param>
        public bool ApplySolution(object solution)
        {
            if (solution == null) throw new ExceptionRule(this, "Failed to apply precalculated solution. Null parameter (solution) passed");

            return false;

            // in, here, out
            // solution.
        }

        /// <summary>
        /// 	validate rule (check if the IN righr rules - are correct)
        /// </summary>
        /// <returns>true if rule is valid</returns>
        /// <exception>Throws an exception that explains the cause </exception>
        public bool IsValid()
        {
            // check if rule is associated with a membrane
            if (ParentMembrane == null)
                throw new ExceptionRule(this, "Rule is not associated with a memrane");

            if (LeftHandRule == null)
                throw new ExceptionRule(this, "Left hand rule is not defined");

            if (RightHandRules == null || RightHandRules.Count == 0)
                throw new ExceptionRule(this, "Right hand rules not defined at all");

            if (String.IsNullOrWhiteSpace(Label))
                throw new ExceptionRule(this, "Label not set for this rule");

            // check each right hand rule
            foreach (RightHandRule rule in RightHandRules)
            {
                // check mandatory target memebrane label for IN rulles
                if (rule.TargetType == RuleTargets.In)
                {
                    if (String.IsNullOrWhiteSpace(rule.TargetMembraneLabel))
                        throw new ExceptionRule(this, "Target memebrane label must be deginede for IN rght hand rule.");

                    // check validity of target membrane - at least one membrane should be available
                    var isReacheable = false;
                    foreach (MembranesTree membrane in ParentMembrane.Membranes)
                    {
                        if (membrane.Label.Equals(rule.TargetMembraneLabel))
                        {
                            isReacheable = true;
                            break;
                        }
                    }

                    if (!isReacheable) throw new ExceptionRule(this, "Membrane in rule is not reacheable");
                }
            }

            return true;
        }

        /// <summary>
        ///     Determines the probability (the weight of execution probability)
        /// of the selected rule. This method is preferable used in combination
        /// with isValid method.
        /// </summary>
        /// <param name="void"></param>
        /// <returns></returns>
        public long getRuleExecutionCoeficient()
        {
            return RuleExecutionCoeficient.WillExecuteUsually;
        }

        /// <summary>
        ///     Literal rule description of current rule
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            string rightPart = "";

            if (RightHandRules == null) return "";

            foreach (var rl in RightHandRules)
            {
                if (rl == null) continue;

                if (rightPart.Length > 0)
                    rightPart += " || ";

                rightPart += rl.ToString();
            }

            return String.Format("{0}: {1} --> {2}", Label != null ? Label : "?",
                LeftHandRule != null ? LeftHandRule.ToString() : "", rightPart);
        }

        /// <summary>
        ///     Apply current rule by passing solution to each right hand part
        /// </summary>
        /// <param name="so"></param>
        /// <returns></returns>
        public override void ApplyRule(Solution solution, psystems.PSystemsTree parrentConfig)
        {
            // for each right hand rule, apply rules);
            foreach (RightHandRule rule in RightHandRules)
            {
                rule.ApplyRule(solution, parrentConfig);

            }


            // remove processed multiset ?: Probably it should be removed after all solutions were processed
            MembranesTree targetMembrane = MembranesTree.SearchMembraneById(parrentConfig.Membranes.Membranes, solution.Membrane.Id);

            if (targetMembrane == null)
            {
                Console.WriteLine("ERROR: Cannot find target membrane to remove solution!");
                return;
            }

            targetMembrane.Multiset.RemoveAt(solution.ProcessedStringIndex);

        }
    }
}